package com.kiss.fw.filter;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import com.kiss.fw.action.AbstractAction;
import com.kiss.fw.context.ActionContext;
import com.kiss.fw.utils.Constants;
import com.kiss.fw.utils.Util;

public class ActionFilter extends AbstractFilter {
	private static final Logger logger = Logger.getLogger(ActionFilter.class);
	private static Map<String, String> actionsClassMap = null;
	private static Map<String, AbstractAction> actionsMap = null;
	private static Map<AbstractAction, Map<String, List<String>>> actionsRoles = null;
	
	@Override
	protected void initialize() throws Exception {
		List<String> facesKeys = Util.getPropertyKeys("action.faces.");
		
		actionsClassMap = new HashMap<String, String>(facesKeys.size());
		actionsMap = new HashMap<String, AbstractAction>(facesKeys.size());
		actionsRoles = new HashMap<AbstractAction, Map<String, List<String>>>(facesKeys.size());
		
		for(int i=0; i < facesKeys.size(); i++) {
			try {
				String actionFacesKey = facesKeys.get(i);
				String actionClassKey = Util.replace(actionFacesKey, "faces", "class");
				String actionRolesKey = Util.replace(actionFacesKey, "faces", "roles");
				
				String actionClass = Util.getProperty(actionClassKey);
				String[] actionUrls = Util.toStringArray(Util.getProperty(actionFacesKey), ",");
				
				// load action class
				AbstractAction actionClassInstance = (AbstractAction)Class.forName(actionClass).newInstance();
				
				// CRUD roles for the action
				String createRole = Util.getProperty(actionRolesKey +".create");
				String readRole = Util.getProperty(actionRolesKey +".read");
				String updateRole = Util.getProperty(actionRolesKey +".update");
				String deleteRole = Util.getProperty(actionRolesKey +".delete");
				String searchRole = Util.getProperty(actionRolesKey +".search");
				
				List<String> createRoles = Util.isStringEmpty(createRole) ? null : Arrays.asList(Util.toStringArray(createRole, ","));
				List<String> readRoles = Util.isStringEmpty(readRole) ? null : Arrays.asList(Util.toStringArray(readRole, ","));
				List<String> updateRoles = Util.isStringEmpty(updateRole) ? null : Arrays.asList(Util.toStringArray(updateRole, ","));
				List<String> deleteRoles = Util.isStringEmpty(deleteRole) ? null : Arrays.asList(Util.toStringArray(deleteRole, ","));
				List<String> searchRoles = Util.isStringEmpty(searchRole) ? null : Arrays.asList(Util.toStringArray(searchRole, ","));
				
				int crudMapSize = (createRoles == null ? 0 : 1) + (readRoles == null ? 0 : 1) + (updateRoles == null ? 0 : 1) + (deleteRoles == null ? 0 : 1) + (searchRoles == null ? 0 : 1);
				Map<String, List<String>> crudMap = null;
				
				if(crudMapSize > 0) {
					crudMap = new HashMap<String, List<String>>(crudMapSize);
					
					if(createRoles != null) {
						crudMap.put(Constants.Mappings.CREATE_ACTION, createRoles);
					}
					
					if(readRoles != null) {
						crudMap.put(Constants.Mappings.READ_ACTION, readRoles);
					}
					
					if(updateRoles != null) {
						crudMap.put(Constants.Mappings.UPDATE_ACTION, updateRoles);
					}
					
					if(deleteRoles != null) {
						crudMap.put(Constants.Mappings.DELETE_ACTION, deleteRoles);
					}
					
					if(searchRoles != null) {
						crudMap.put(Constants.Mappings.SEARCH_ACTION, searchRoles);
					}
					
					actionsRoles.put(actionClassInstance, crudMap);
				}
				
				for(String __actionUrl : actionUrls) {
					// updating the class map, just in case loading failed
					// during the initialize() phase.
					actionsClassMap.put(__actionUrl, actionClass);
					
					// add to map
					actionsMap.put(__actionUrl, actionClassInstance);
					
					// log
					if(logger.isDebugEnabled()) {
						logger.debug(actionClassInstance +" - " +__actionUrl);
					}
					
				}
			}
			catch(Exception _exception) {
				logger.error("Error initializing Action Class", _exception);
			}
		}
	}
	
	@Override
	protected void execute(HttpServletRequest _request, HttpServletResponse _response) throws Exception {
		AbstractAction action = actionsMap.get(getRequestedUri(_request));
		
		// not all requests are actions
		if(action != null) {
			ActionContext context = new ActionContext(_request, _response);
			
			if(authorize(action, context)) {
				if(validate(action, context)) {
					action.execute(context);
				}
				else {
					throw Constants.Exceptions.HTTP_400_EXCEPTION;
				}
			}
			else {
				throw Constants.Exceptions.HTTP_403_EXCEPTION;
			}
			
			// TODO: Check if this is valid code
			context = null;
		}
	}
	
	protected static boolean authorize(final AbstractAction _action, final ActionContext _context) {
		boolean returnVal = true;
		
		// check if action is secured
		if(actionsRoles.containsKey(_action)) {
			List<String> checkRoles = null;
			
			// checking if role authorization is required
			if(_context.isCreateAction()) {
				checkRoles = actionsRoles.get(_action).get(Constants.Mappings.CREATE_ACTION);
			}
			else if(_context.isReadAction()) {
				checkRoles = actionsRoles.get(_action).get(Constants.Mappings.READ_ACTION);
			}
			else if(_context.isUpdateAction()) {
				checkRoles = actionsRoles.get(_action).get(Constants.Mappings.UPDATE_ACTION);
			}
			else if(_context.isDeleteAction()) {
				checkRoles = actionsRoles.get(_action).get(Constants.Mappings.DELETE_ACTION);
			}
			else if(_context.isSearchAction()) {
				checkRoles = actionsRoles.get(_action).get(Constants.Mappings.SEARCH_ACTION);
			}
			
			// user needs to be in ALL the roles specified for the action
			if(checkRoles != null) {
				for(String __role : checkRoles) {
					if(!_context.getRequest().isUserInRole(__role)) {
						returnVal = false;
						break;
					}
				}
			}
		}
		
		return returnVal;
	}
	
	/**
	 * Performs action validation by first performing the framework validation and then
	 * validation implemented by the action class.
	 * 
	 * @param AbstractAction _action
	 * @param ActionContext _context
	 * @return boolean
	 */
	protected static boolean validate(final AbstractAction _action, final ActionContext _context) {
		boolean returnVal = true;
		
		// TODO: Implement framework validation
		
		if(returnVal) {
			returnVal = _action.validate(_context);
		}
		
		return returnVal;
	}
}